Skip to content

fix(agent-core): honor abort signal in WebSearch and FetchURL tools - #1108

Open
Caldalis wants to merge 1 commit into
MoonshotAI:mainfrom
Caldalis:fix/web-tools-abort-signal
Open

fix(agent-core): honor abort signal in WebSearch and FetchURL tools#1108
Caldalis wants to merge 1 commit into
MoonshotAI:mainfrom
Caldalis:fix/web-tools-abort-signal

Conversation

@Caldalis

Copy link
Copy Markdown

Related Issue

Resolve #1106

Problem

WebSearch and FetchURL ignored the per-call AbortSignal. Cancelling a turn
(Ctrl-C / turn abort) did not cancel an in-flight network request — it kept
running in the background, holding a connection, and on a slow or hung server it
lingered until undici's default timeouts.

Every other I/O-bound builtin tool (Bash, Grep, Agent, AskUser) already threads
ctx.signal into its work; the two web tools were the only exception, with no
comment explaining the omission, so this reads as an overs
intentional choice.

What changed

  • Added an optional signal to the UrlFetcher and WebSearchProvider option objects.
  • FetchURLTool / WebSearchTool now forward ctx.signal they destructured only toolCallId`).
  • LocalFetchURLProvider, MoonshotFetchURLProvider, and MoonshotWebSearchProvider pass the signal to the underlying fetch(), which aborts both the header wait and the body read. The Moonshot → loc options object, so the signal flows through automatically.
  • Tests: updated the call-shape assertions for the two tools and added provider tests asserting the signal reaches the underlying fetch.

This matches the existing cancellation pattern used by Bash/Grep/Agent and keeps the change focused. The touched interfaces (UrlFetcher /
WebSearchProvider) are internal to agent-core and are rface.

Checklist

  • I have read the CONTRIBUTING document.
  • I have linked a related issue, or explained the problem above.
  • I have added tests that prove my feature works.
  • Ran gen-changesets skill, or this PR needs no changeset.
  • Ran gen-docs skill, or this PR needs no doc update

@changeset-bot

changeset-bot Bot commented Jun 26, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: a1c5358

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 1 package
Name Type
@moonshot-ai/kimi-code Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@itxaiohanglover

Copy link
Copy Markdown

Clean threading of the abort signal through every layer — nice work. Tests covering both providers are solid. One small thing: MoonshotFetchURLProvider.fetch has a catch-all that swallows errors into empty content, so an abort there would surface as an 'empty content' error rather than a clean abort. Might be worth letting AbortError propagate, though it's a pre-existing pattern so no blocker.

@Caldalis

Copy link
Copy Markdown
Author

Clean threading of the abort signal through every layer — nice work. Tests covering both providers are solid. One small thing: MoonshotFetchURLProvider.fetch has a catch-all that swallows errors into empty content, so an abort there would surface as an 'empty content' error rather than a clean abort. Might be worth letting AbortError propagate, though it's a pre-existing pattern so no blocker.

Thanks addressed in a follow-up: MoonshotFetchURLProvider.fetch now rethrows when the caller's signal is aborted, so a cancelled request surfaces as a clean abort instead of kicking off a fallback fetch. Genuine Moonshot failures (network errors) still fall back to the local fetcher as before. Added a test covering the abort doesn't fall back path.

@Caldalis
Caldalis force-pushed the fix/web-tools-abort-signal branch from 120345b to a41a165 Compare July 17, 2026 16:20
@Caldalis

Copy link
Copy Markdown
Author

I've rebased this onto the latest main and refreshed the branch. #1791 ("close FetchURL SSRF bypasses and DNS-rebinding window") landed and rewrote LocalFetchURLProvider the actual fetch call moved into the per-hop redirect loop (requestWithValidatedRedirects), so I re-threaded the abort signal through that new structure instead of the old top-level fetch. WebSearch was also slimmed to query-only (#1260), so the provider options were updated to match.
The intent is unchanged:

  • Thread the turn's AbortSignal from the FetchURL / WebSearch tool context through the UrlFetcher / WebSearchProvider interfaces down to every underlying fetch (the Moonshot fetch/search providers and the local fallback's per-hop request).
  • A caller-driven abort of the Moonshot fetch is now re-thrown instead of retrying the local fallback so cancellation surfaces cleanly rather than silently re-running the request.
    Lint, typecheck, and the web-tool tests are green, and a changeset is included.

Thread the turn's AbortSignal through the FetchURL/WebSearch tools and the UrlFetcher / WebSearchProvider interfaces into every underlying fetch, so cancelling a turn aborts the in-flight request instead of leaving it running. Covers the Moonshot fetch/search providers and the local fallback fetcher's per-hop request. A caller-driven abort of the Moonshot fetch is re-thrown instead of retrying the local fallback, so cancellation surfaces cleanly.
@Caldalis
Caldalis force-pushed the fix/web-tools-abort-signal branch from a41a165 to a1c5358 Compare July 18, 2026 04:41
xsyetopz added a commit to xsyetopz/kimi-code that referenced this pull request Jul 31, 2026
Phase 1 — Non-PR issues (5):

- MoonshotAI#94: Read tool status now reports both MAX_LINES and MAX_BYTES when both
  limits are hit (was: else-if chain only reported the first). Fixed in v1
  (agent-core) and v2 (agent-core-v2). Added test for dual-limit case.

- MoonshotAI#250: KIMI_MODEL_* env config no longer gives OpenAI/Anthropic models the
  Kimi default capabilities (image_in, thinking). DEFAULT_CAPABILITIES is now
  a per-provider-type map: kimi gets ['image_in','thinking'], openai/anthropic
  get []. Fixed in v1 (env-model.ts) and v2 (envOverlay.ts). Added 2 tests.

- MoonshotAI#1972: Skill descriptions exceeding the 250-char model listing limit now
  emit a warning via onWarning during loadRoots, so the user knows the model
  sees a truncated version. Added 2 tests (warns / does not warn).

- MoonshotAI#1982: Added disabled_skills config denylist. config.toml now accepts
  disabled_skills = ['skill-name']. Matching skills (case-insensitive) are
  hidden from getSkill, getPluginSkill, listSkills, listInvocableSkills,
  register, loadRoots, and the TUI slash menu. Wired through SessionSkillConfig,
  resolveSessionSkillConfig, and TOML serialization. Added 3 tests.

- MoonshotAI#2224: Plugin-qualified skill names (e.g. 'superpowers:systematic-debugging')
  are now resolved by the native Skill tool. Falls back to getPluginSkill when
  bare-name getSkill misses and the name contains ':'. Fixed in v1 (skill-tool.ts)
  and v2 (skillTool.ts). Added 2 tests.

Phase 2 — PR-matching issues (5):

- MoonshotAI#1008 (PR MoonshotAI#1096): Web UI session archive relabeled from 'Archive' to 'Delete'.
  Confirm message now states the action is irreversible (en + zh).

- MoonshotAI#1106 (PR MoonshotAI#1108): WebSearch and FetchURL tools now forward the abort signal
  from ExecutableToolContext through to fetch() calls. Updated provider
  interfaces (WebSearchProvider, UrlFetcher) and all 3 provider implementations
  (MoonshotWebSearch, MoonshotFetchURL, LocalFetchURL). Updated 2 tests.

- MoonshotAI#1050 (PR MoonshotAI#1052): kosong streaming response loop now has an idle timeout
  (default 60s, configurable via idleTimeoutMs). Replaced bare for-await with
  manual iterator + Promise.race against timeout. Throws APITimeoutError on
  idle. Fixed in v1 (kosong/generate.ts) and v2 (agent-core-v2/generate.ts).
  Added 9 tests.

- MoonshotAI#1148 (PR MoonshotAI#1208): max_tokens/max_output_tokens now clamped to 128k ceiling
  in openai-legacy generate() and openai-responses withMaxCompletionTokens() +
  generate(). Kimi provider left unchanged (upstream clamping is intentional).
  Added 4 tests.

- MoonshotAI#1273 (PR MoonshotAI#1276): Added KimiCore.dispose() which iterates all live sessions
  and calls session.close() via Promise.allSettled. Wired into
  CoreProcessService.dispose(). Idempotent. Added 5 tests.

Skipped (already fixed in fork): MoonshotAI#1539, MoonshotAI#244, MoonshotAI#1218.
Checklist: .tmp/upstream-tracking/ISSUE-RESOLUTION-CHECKLIST.md
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Bug: web-tools-abort-signal

2 participants